Compile-time extension/option discovery source generator#371
Merged
Conversation
Generalizes the existing command-discovery pattern to "extension"/"option" types so consuming frameworks (Wolverine, Marten, ...) can eliminate runtime assembly scanning (e.g. Wolverine's ExtensionLoader + AssemblyFinder.FindAssemblies filesystem probe). - IJasperFxExtension: marker interface; IServiceRegistrations now extends it. Framework extension interfaces (e.g. Wolverine's IWolverineExtension) extend it to be discoverable. - ExtensionDiscoveryGenerator (JasperFx.SourceGenerator): emits a per-assembly JasperFx.Generated.DiscoveredExtensions.ExtensionTypes manifest, gated to assemblies that carry a [JasperFxAssembly]-derived attribute OR are executable (entry) assemblies. Discovers via two deduped sources: (1) the type declared by the assembly's [JasperFxAssembly] / [WolverineModule<T>] attribute (generic args + typeof ctor args), and (2) concrete classes implementing IJasperFxExtension. - GeneratedExtensionManifest: runtime reader that aggregates the manifests from loaded assemblies; consumers filter by their own extension interface. - JasperFx.SourceGenerator.Tests: covers the eligibility gate, both discovery sources, dedup, and abstract/static exclusion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
This was referenced May 26, 2026
Closed
erdtsieck
pushed a commit
to erdtsieck/jasperfx
that referenced
this pull request
May 29, 2026
…rFx#373) Extends the compile-time discovery from JasperFx#371 so the source generator emits actual IServiceCollection registrations, not just a type list. - Add [JasperFxService(typeof(serviceType), ServiceLifetime)] to the JasperFx assembly (AllowMultiple), letting a concrete class declare one or more DI registrations. - New ServiceRegistrationGenerator emits a per-assembly JasperFx.Generated.GeneratedServiceRegistrations.Register(IServiceCollection) full of plain services.Add(new ServiceDescriptor(...)) calls -- reflection-free, trim/AOT-clean. Open-generic service types (typeof(IValidator<>)) are closed from the implementation's implemented interface (=> IValidator<Foo>). Same eligibility gate as ExtensionDiscoveryGenerator ([JasperFxAssembly] or executable). - Add runtime aggregator GeneratedExtensionManifest.RegisterAllDiscoveredServices(IServiceCollection) (+ an assemblies overload + AnyServiceRegistrationsPresent) that invokes each loaded assembly's generated Register method; trim-suppressed; no-op fallback when the generator wasn't run. - Tests: generator text + eligibility + open/closed generics + multiple attributes per impl + a compile-verification test (emitted code compiles against the full framework reference set) + runtime aggregator tests. README documents [JasperFxService] and the aggregator and now lists all five bundled generators. Scope: this first increment is the attribute mechanism (option A), covering the IServiceRegistrations, IWolverineExtension (Singleton-against-interface) and closed-generic IValidator<Foo> shapes. The MSBuild-config path for external interfaces (option C in the issue) is deferred to a follow-up. Refs JasperFx#373. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Generalizes the existing command discovery pattern (
CommandDiscoveryGenerator→DiscoveredCommandsmanifest) to extension / option types, so consuming Critter Stack frameworks can eliminate runtime assembly scanning for their extensions (e.g. Wolverine'sExtensionLoader+AssemblyFinder.FindAssembliesfilesystem probe +Assembly.Load).IJasperFxExtensionIServiceRegistrationsnow extends it; framework extension interfaces (Wolverine'sIWolverineExtension, etc.) extend it to be discoverable.ExtensionDiscoveryGenerator(JasperFx.SourceGenerator)JasperFx.Generated.DiscoveredExtensions.ExtensionTypesmanifest, gated to assemblies carrying a[JasperFxAssembly]-derived attribute or executable (entry) assemblies.GeneratedExtensionManifestAnyManifestPresent()so consumers can fall back to reflective discovery when the generator isn't active.JasperFx.SourceGenerator.TestsDiscovery (deduped, two sources)
[WolverineModule<T>]-style attributes andtypeof(...)constructor args of[JasperFxAssembly(typeof(T))].IJasperFxExtensionin the compilation.This "support both" approach is migration-friendly: existing
[WolverineModule<T>]/[JasperFxAssembly(typeof(T))]declarations keep working, and new code can simply implement the marker.Tests
ExtensionDiscoveryGeneratorTestscovers: the eligibility gate (executable vs[JasperFxAssembly]vs plain library), marker-interface discovery, the generic-attribute declared-type path, dedup, and abstract/static exclusion. 5/5 green; JasperFx core builds clean.Scope / follow-up
This PR is the JasperFx-side enabler — nothing consumes the manifest yet, so it's inert until a framework opts in. A follow-up Wolverine issue will adapt
ExtensionLoaderto consumeGeneratedExtensionManifest(with the goal of removingExtensionLoader's runtime scan entirely), makeIWolverineExtension : IJasperFxExtension, and reference the analyzer fromWolverineFx.🤖 Generated with Claude Code